package provisioner;

import provisioner.security.CertificateGenerator;
import provisioner.security.PublicPrivateKeysGenerator;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Scanner;

import com.jcraft.jsch.*;

public class EucaVmNode extends ComputeNode implements Runnable {

   public static final int INIT_PERIOD = 10*1000;   // milliseconds
   public static final int SSH_TIMEOUT = 60*1000; // milliseconds.

   private String eucaIpAddress;
   private String vpnIpAddress;
   private String property;
   
   private Session sshSession;

   /*
    * Cluster environments
    */
   private String vpnServerIp;
   private String privateIp;
   private String privateNetwork;
   private String privateNetmask;
   private String privateKeyFile;
   private String CACertFile;

   private PrintStream logFi;
   private Thread workingThread;

   public EucaVmNode(String id) {
      super(id);
   }

   public EucaVmNode(String id,int state) {
      super(id,state);
   }

   public synchronized void setEucaIpAddress(String ipAddress) {
      this.eucaIpAddress = ipAddress;
   }

   public synchronized void setVpnIpAddress(String ipAddress) {
      this.vpnIpAddress = ipAddress;
   }

   public synchronized String getVpnIpAddress() {
      return this.vpnIpAddress;
   }

   public synchronized void setSshSession(Session sshSession) {
      this.sshSession = sshSession;
   }

   public synchronized Session getSshSession() {
      return this.sshSession;
   }

   public synchronized void setProperty(String prop) {
      this.property = prop;
   }
  
   public synchronized String getProperty() {
      return this.property;
   }

   public synchronized void setWorkingThread(Thread t) {
      this.workingThread = t;
   }

   public synchronized Thread getWorkingThread() {
      return this.workingThread;
   }

   public synchronized void setContext(String vpnServerIp, String privateIp, String privateNetwork, String privateNetmask, String privateKeyFile, String CACertFile) {
      this.vpnServerIp = vpnServerIp;
      this.privateIp = privateIp;
      this.privateNetwork = privateNetwork;
      this.privateNetmask = privateNetmask;
      this.privateKeyFile = privateKeyFile;
      this.CACertFile = CACertFile;
   }

   public synchronized void initialize() throws Exception {
      Session sshSession = getSshSession();
      if (sshSession == null) {
         JSch jsch = new JSch();
         jsch.addIdentity(this.privateKeyFile);
         sshSession = jsch.getSession("root",getHostname());
         sshSession.setConfig("StrictHostKeyChecking","no");
         setSshSession(sshSession);
      }

      if (!sshSession.isConnected()) {
         try {
            sshSession.connect(SSH_TIMEOUT);
         } catch (com.jcraft.jsch.JSchException e) {
            System.err.println("Cannot connection to " + getId() + ": " + e);
            return;
         }
      }

      if (logFi == null) {
         String logName = "/tmp/" + getId() + ".log";
         try {
            logFi = new PrintStream(new FileOutputStream(logName),true);
         } catch (Exception e) {
            logFi = System.out;
            System.err.println("Cannot write file " + logName);
            e.printStackTrace();
         }
      }

      switch (getState()) {
         case ComputeNode.STARTED:
            logFi.println("Start initializing ..." + new Date());
            logFi.println(getId() + " creating VPN");
            createVpn();   // openvpn tunnel to private networks
            setState(ComputeNode.VPNNING);
            //break; // continue to next stage
        case ComputeNode.VPNNING:
            logFi.println("Continue initializing ..." + new Date());
            logFi.println(getId() + " checking VPN connected");
            String vpnClientIp = checkVpnClientIp();
            if ( (vpnClientIp != null) && (!vpnClientIp.equals("")) ) {
               logFi.println(getId() + " running init script");
               setVpnIpAddress(vpnClientIp);
               sync411Config();   // sync 411 configuration files
               runInitScript();
               //setupHostname(); // set hostname,authorized key

               getSshSession().disconnect();
               setSshSession(null);
               setState(ComputeNode.CONTEXUALIZED);
               logFi.println(getId() + " is contexualized " + new Date());
            }
            break;
      }


   }

   public void run() {
      while (true) {
         try {
            initialize();
            if (getState() == ComputeNode.READY) {
               break;
            }
         } catch (Exception e) {
            if (logFi != null) {
               e.printStackTrace(logFi);
            } else {
               e.printStackTrace();
            }
         }
         try {
            Thread.sleep(INIT_PERIOD);
         } catch (InterruptedException e) {
            logFi.println("Thread wake up earlier than expected"); 
         }
      }
      if (logFi != System.out) {
         logFi.close();
      }
   }

   private void createVpn() throws Exception {

      Session sshSession = getSshSession();
      KeyPair keyPair = PublicPrivateKeysGenerator.generateKeys("RSA",1024); // public-private keys for a new instance
      CertificateGenerator certGen = new CertificateGenerator("/home/ekasit/src/typica-1.7/ca.key","/home/ekasit/src/typica-1.7/ca.crt");
      X509Certificate x509Cert = certGen.generateCertificate(getId(),keyPair);
      String privateKey = PublicPrivateKeysGenerator.convertToPEM(keyPair.getPrivate());
      String cert = CertificateGenerator.convertToPEM(x509Cert);

      ChannelSftp sftp = (ChannelSftp) sshSession.openChannel("sftp");
      sftp.connect();
      sftpCreateFile(sftp,"/etc/security/client.crt",cert);
      sftpCreateFile(sftp,"/etc/security/client.key",privateKey,600);
      File CACert = new File(CACertFile);
      sftpCreateFile(sftp,"/etc/security/ca.crt",CACert);
      String vpnConf = generateVpnClientConfig(vpnServerIp,privateNetwork,privateNetmask);
      sftpCreateFile(sftp,"/etc/openvpn/client.conf",vpnConf);
      // Use up-script.sh to change resolv.conf instead
      //String resolvConf = generateResolvConfig("10.8.0.1");
      //sftpCreateFile(sftp,"/etc/resolv.conf",resolvConf);
      String dhcpClientHook = generateDhcpClientHook();
      sftpCreateFile(sftp,"/etc/dhclient-enter-hooks",dhcpClientHook);
      sftp.disconnect();

sshExecCommand(sshSession,"sed '/^exit$/d' /etc/init.d/openvpn > /etc/init.d/openvpn.x; mv /etc/init.d/openvpn.x /etc/init.d/openvpn; chmod +x /etc/init.d/openvpn");
      sshExecCommand(sshSession,"service openvpn restart");

   }

   public synchronized void sync411Config() throws Exception {

      Session sshSession = getSshSession();
      ChannelSftp sftp = (ChannelSftp) sshSession.openChannel("sftp");
      sftp.connect();
      String four11Conf = generate411ClientConfig(privateIp);
      sftpCreateFile(sftp,"/etc/411.conf",four11Conf);
      File pubKey = new File("/etc/411-security/master.pub");
      sftpCreateFile(sftp,"/etc/411-security/master.pub",pubKey);
      File sharedKey = new File("/etc/411-security/shared.key");
      sftpCreateFile(sftp,"/etc/411-security/shared.key",sharedKey,400);

sftpCreateFile(sftp,"/root/rocks-411-4.2.1-0.noarch.rpm",new File("/state/partition1/home/install/rolls/hpc/4.2.1/ia64/RedHat/RPMS/rocks-411-4.2.1-0.noarch.rpm"));

logFi.println("Downgrading 411");
sshExecCommand(sshSession,"rpm -i --force /root/rocks-411-4.2.1-0.noarch.rpm");

      /*
       * To prevent NFS mount fail during the named at frontend 
       * restarting. The better way would be to reload named instead.
       */
      sftpCreateFile(sftp,"/etc/hosts",new File("/etc/hosts"),644);

      sftp.disconnect(); 

      sshExecCommand(sshSession,"/opt/rocks/bin/411get --all; service autofs restart");

   }

   public synchronized void runInitScript() throws Exception {
      String initScript = generateInitScript();

      Session sshSession = getSshSession();
      ChannelSftp sftp = (ChannelSftp) sshSession.openChannel("sftp");
      sftp.connect();
      sftpCreateFile(sftp,"/root/initscript.sh",initScript,755);
      sftp.disconnect();

      sshExecCommand(sshSession,"/root/initscript.sh");

   }

   private String generateVpnClientConfig(String vpnServer,String privateNetwork,String privateNetmask) {
      String vpnConf = "";
      vpnConf = "client\n" +
                "dev tun\n" +
                "remote " + vpnServer + "\n" +
                "ca /etc/security/ca.crt\n" +
                "cert /etc/security/client.crt\n" +
                "key /etc/security/client.key\n" +
                "route " + privateNetwork + " " + privateNetmask + "\n" +
                "script-security 2\n" +
                "auth-retry nointeract\n" +  
                "up /etc/openvpn/up-script.sh\n" +
                "down /etc/openvpn/down-script.sh\n";
      return vpnConf;
   }

   public String checkVpnClientIp() throws Exception {
      String vpnClientIp = null;
      Session sshSession = getSshSession();
      StringBuffer stdoutBuf = new StringBuffer();
      sshExecCommand(sshSession,"cat /etc/openvpn/ip.txt 2> /dev/null",stdoutBuf);
//      sshExecCommand(sshSession,"/sbin/ifconfig tun0 | grep 'inet addr' | cut -d : -f 2| awk '{print $1}'",stdoutBuf);
      try {
         Scanner sc = new Scanner(stdoutBuf.toString());
         vpnClientIp = sc.nextLine();
         sc.close();
      } catch (Exception e) {
         e.printStackTrace(logFi);
      }
      return vpnClientIp;
   }

   private String generate411ClientConfig(String serverIp) {
      String four11Conf = "";
      four11Conf = "<config>\n" +
                   "<master url=\"http://" + serverIp + "/411.d/\"/>\n" +
                   "<group>Compute</group>\n" +
                   "</config>\n";
      return four11Conf;
   }

   private String generateResolvConfig(String dnsServer) {
      String resolvConf = "";
      resolvConf = "search local\n" +
                   "nameserver 10.8.0.1\n";
      return resolvConf;
   }

   /*
    * Prevent dhcp client update the resolv.conf
    */
   private String generateDhcpClientHook() {
      String hook = "";
      hook = "make_resolv_conf() {\n" +
             "   echo 'doing nothing to resolv.conf' > /dev/null\n" +
             "}\n";
      return hook;
   }

   private String generateInitScript() throws Exception {
      String initScript = "";
      initScript = "#!/bin/sh\n" +
                   "echo 10.8.0.1 > /var/spool/torque/server_name\n" +
                   "echo '10.8.0.1    vpnserver' >> /etc/hosts\n" +
                   "service pbs_mom restart\n" +
                   "echo 'HOSTNAME=" + getId() + "' >> /etc/sysconfig/network\n" +
                   "hostname " + getId() + "\n";
      String authorizedKeys = null;
      try {
         authorizedKeys = getAuthorizedKeys();
      } catch (Exception e) {
         logFi.println(e);
      }
      if (authorizedKeys != null) {
         initScript += "cat >> /root/.ssh/authorized_keys << EOF\n" +
                       authorizedKeys + "\n" +
                       "EOF\n";
      }

      return initScript;
   }

   private void sftpCreateFile(ChannelSftp sftp,String filename,String content) throws Exception {
      InputStream is = new ByteArrayInputStream(content.getBytes());
      sftp.put(is,filename);
      is.close();
   }

   private void sftpCreateFile(ChannelSftp sftp,String filename,String content,int permissions) throws Exception {
      InputStream is = new ByteArrayInputStream(content.getBytes());
      sftp.put(is,filename);
      is.close();
      sftp.chmod(convertPermissions(permissions),filename);
   }

   private void sftpCreateFile(ChannelSftp sftp,String filename,File fi) throws Exception {
      InputStream is = new FileInputStream(fi);
      sftp.put(is,filename);
      is.close();
   }

   private void sftpCreateFile(ChannelSftp sftp,String filename,File fi,int permissions) throws Exception {
      InputStream is = new FileInputStream(fi);
      sftp.put(is,filename);
      is.close();
      sftp.chmod(convertPermissions(permissions),filename);
   }

   private void sshExecCommand(Session sshSession, String command) throws Exception {
      sshExecCommand(sshSession,command,null);
   }

   private void sshExecCommand(Session sshSession, String command, StringBuffer sb) throws Exception {
      ChannelExec exec = (ChannelExec) sshSession.openChannel("exec");
      exec.setInputStream(null);
      ByteArrayOutputStream errBuf = new ByteArrayOutputStream();
      exec.setErrStream(errBuf);
      exec.setCommand(command);
      InputStream stdout = exec.getInputStream();
      BufferedReader br = new BufferedReader(new InputStreamReader(stdout));
      exec.connect();
      String line = null;
      while ((line = br.readLine()) != null) {
         if (sb != null) {
            sb.append(line + "\n");
         } else {
            logFi.println(line);
         }
      }
      br.close();
      logFi.println(errBuf);

      exec.disconnect();
   }

   private String getAuthorizedKeys() throws Exception {
      BufferedReader reader = new BufferedReader(new FileReader("/root/.ssh/authorized_keys"));
      StringBuffer sb = new StringBuffer(1024);
      char[] buf = new char[1024];
      int numRead = 0;
      while ((numRead=reader.read(buf)) != -1) {
         sb.append(String.valueOf(buf,0,numRead));
      }
      reader.close();

      return sb.toString();
   }


   private int convertPermissions(int permissions) throws Exception {
      int foo = 0;
      byte[] bar=(""+permissions).getBytes();
      int k;
      for (int j=0; j<bar.length; j++) {
          k=bar[j];
          if (k<'0'||k>'7'){foo=-1; break;}
             foo<<=3;
          foo|=(k-'0');
       }
       if (foo==-1)
          throw new NumberFormatException("Invalid file permissions");
       return foo;
   }


}
